/* Copyright (c) 2010, Carl Burch. License information is located in the
 * com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */

package com.cburch.draw.shapes;

import java.awt.Graphics;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.collections15.list.UnmodifiableList;

import com.cburch.draw.model.CanvasObject;
import com.cburch.draw.model.Handle;
import com.cburch.draw.model.HandleGesture;
import com.cburch.logisim.data.Bounds;
import com.cburch.logisim.data.Location;

abstract class Rectangular extends FillableCanvasObject {
    // excluding the stroke's width
    private Bounds bounds;

    public Rectangular(int x, int y, int w, int h) {
        bounds = Bounds.create(x, y, w, h);
    }

    @Override
    public boolean matches(CanvasObject other) {
        if (other instanceof Rectangular) {
            Rectangular that = (Rectangular) other;
            return this.bounds.equals(that.bounds)
                && super.matches(that);
        } else {
            return false;
        }
    }

    @Override
    public int matchesHashCode() {
        return bounds.hashCode() * 31 + super.matchesHashCode();
    }

    public int getX() {
        return bounds.getX();
    }

    public int getY() {
        return bounds.getY();
    }

    public int getWidth() {
        return bounds.getWidth();
    }

    public int getHeight() {
        return bounds.getHeight();
    }

    @Override
    public Bounds getBounds() {
        int wid = getStrokeWidth();
        Object type = getPaintType();
        if (wid < 2 || type == DrawAttr.PAINT_FILL) {
            return bounds;
        } else {
            return bounds.expand(wid / 2);
        }
    }

    @Override
    public void translate(int dx, int dy) {
        bounds = bounds.translate(dx, dy);
    }

    @Override
    public List<Handle> getHandles(HandleGesture gesture) {
        return UnmodifiableList.decorate(Arrays.asList(getHandleArray(gesture)));
    }

    private Handle[] getHandleArray(HandleGesture gesture) {
        Bounds bds = bounds;
        int x0 = bds.getX();
        int y0 = bds.getY();
        int x1 = x0 + bds.getWidth();
        int y1 = y0 + bds.getHeight();
        if (gesture == null) {
            return new Handle[] { new Handle(this, x0, y0),
                    new Handle(this, x1, y0), new Handle(this, x1, y1),
                    new Handle(this, x0, y1) };
        } else {
            int hx = gesture.getHandle().getX();
            int hy = gesture.getHandle().getY();
            int dx = gesture.getDeltaX();
            int dy = gesture.getDeltaY();
            int newX0 = x0 == hx ? x0 + dx : x0;
            int newY0 = y0 == hy ? y0 + dy : y0;
            int newX1 = x1 == hx ? x1 + dx : x1;
            int newY1 = y1 == hy ? y1 + dy : y1;
            if (gesture.isShiftDown()) {
                if (gesture.isAltDown()) {
                    if (x0 == hx) {
                    	newX1 -= dx;
                    }
                    if (x1 == hx) {
                    	newX0 -= dx;
                    }
                    if (y0 == hy) {
                    	newY1 -= dy;
                    }
                    if (y1 == hy) {
                    	newY0 -= dy;
                    }

                    int w = Math.abs(newX1 - newX0);
                    int h = Math.abs(newY1 - newY0);
                    // reduce width to h
                    if (w > h) {
                        int dw = (w - h) / 2;
                        newX0 -= (newX0 > newX1 ? 1 : -1) * dw;
                        newX1 -= (newX1 > newX0 ? 1 : -1) * dw;
                    } else {
                        int dh = (h - w) / 2;
                        newY0 -= (newY0 > newY1 ? 1 : -1) * dh;
                        newY1 -= (newY1 > newY0 ? 1 : -1) * dh;
                    }
                } else {
                    int w = Math.abs(newX1 - newX0);
                    int h = Math.abs(newY1 - newY0);
                    // reduce width to h
                    if (w > h) {
                        if (x0 == hx) {
                            newX0 = newX1 + (newX0 > newX1 ? 1 : -1) * h;
                        }
                        if (x1 == hx) {
                            newX1 = newX0 + (newX1 > newX0 ? 1 : -1) * h;
                        }
                    // reduce height to w
                    } else {
                        if (y0 == hy) {
                            newY0 = newY1 + (newY0 > newY1 ? 1 : -1) * w;
                        }
                        if (y1 == hy) {
                            newY1 = newY0 + (newY1 > newY0 ? 1 : -1) * w;
                        }
                    }
                }
            } else {
                if (gesture.isAltDown()) {
                    if (x0 == hx) {
                    	newX1 -= dx;
                    }
                    if (x1 == hx) {
                    	newX0 -= dx;
                    }
                    if (y0 == hy) {
                    	newY1 -= dy;
                    }
                    if (y1 == hy) {
                    	newY0 -= dy;
                    }
                } else {
                    // already handled
                    ;
                }
            }
            return new Handle[] { new Handle(this, newX0, newY0),
                new Handle(this, newX1, newY0), new Handle(this, newX1, newY1),
                new Handle(this, newX0, newY1) };
        }
    }

    @Override
    public boolean canMoveHandle(Handle handle) {
        return true;
    }

    @Override
    public Handle moveHandle(HandleGesture gesture) {
        Handle[] oldHandles = getHandleArray(null);
        Handle[] newHandles = getHandleArray(gesture);
        Handle moved = gesture == null ? null : gesture.getHandle();
        Handle result = null;
        int x0 = Integer.MAX_VALUE;
        int x1 = Integer.MIN_VALUE;
        int y0 = Integer.MAX_VALUE;
        int y1 = Integer.MIN_VALUE;
        int i = -1;
        for (Handle h : newHandles) {
            i++;
            if (oldHandles[i].equals(moved)) {
                result = h;
            }
            int hx = h.getX();
            int hy = h.getY();
            if (hx < x0) {
            	x0 = hx;
            }
            if (hx > x1) {
            	x1 = hx;
            }
            if (hy < y0) {
            	y0 = hy;
            }
            if (hy > y1) {
            	y1 = hy;
            }
        }
        bounds = Bounds.create(x0, y0, x1 - x0, y1 - y0);
        return result;
    }

    @Override
    public void paint(Graphics g, HandleGesture gesture) {
        if (gesture == null) {
            Bounds bds = bounds;
            draw(g, bds.getX(), bds.getY(), bds.getWidth(), bds.getHeight());
        } else {
            Handle[] handles = getHandleArray(gesture);
            Handle p0 = handles[0];
            Handle p1 = handles[2];
            int x0 = p0.getX();
            int y0 = p0.getY();
            int x1 = p1.getX();
            int y1 = p1.getY();
            if (x1 < x0) { 
            	int t = x0; 
            	x0 = x1; 
            	x1 = t; 
            }
            if (y1 < y0) { 
            	int t = y0; 
            	y0 = y1; 
            	y1 = t; 
            }

            draw(g, x0, y0, x1 - x0, y1 - y0);
        }
    }

    @Override
    public boolean contains(Location loc, boolean assumeFilled) {
        Object type = getPaintType();
        if (assumeFilled && type == DrawAttr.PAINT_STROKE) {
            type = DrawAttr.PAINT_STROKE_FILL;
        }
        Bounds b = bounds;
        int x = b.getX();
        int y = b.getY();
        int w = b.getWidth();
        int h = b.getHeight();
        int qx = loc.getX();
        int qy = loc.getY();
        if (type == DrawAttr.PAINT_FILL) {
            return isInRect(qx, qy, x, y, w, h) && contains(x, y, w, h, loc);
        } else if (type == DrawAttr.PAINT_STROKE) {
            int stroke = getStrokeWidth();
            int tol2 = Math.max(2 * Line.ON_LINE_THRESH, stroke);
            int tol = tol2 / 2;
            return isInRect(qx, qy, x - tol, y - tol, w + tol2, h + tol2)
                    && contains(x - tol, y - tol, w + tol2, h + tol2, loc)
                    && !contains(x + tol, y + tol, w - tol2, h - tol2, loc);
        } else if (type == DrawAttr.PAINT_STROKE_FILL) {
            int stroke = getStrokeWidth();
            int tol2 = stroke;
            int tol = tol2 / 2;
            return isInRect(qx, qy, x - tol, y - tol, w + tol2, h + tol2)
                && contains(x - tol, y - tol, w + tol2, h + tol2, loc);
        } else {
            return false;
        }
    }

    boolean isInRect(int qx, int qy, int x0, int y0, int w, int h) {
        return qx >= x0 && qx < x0 + w && qy >= y0 && qy < y0 + h;
    }

    protected abstract boolean contains(int x, int y, int w, int h, Location q);
    protected abstract void draw(Graphics g, int x, int y, int w, int h);
}
